Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this; // 当前调用的方法,就是需要绑定的函数 foo.set.bind(obj) 这里的self就是指foo.set
    var args = Array.prototype.slice.call(arguments, 1); // 获取后面的参数
    var fNOP = function () { };

    var fbound = function () {
        // 当作为构造函数时，this 指向实例，self 指向绑定函数，
        // 因为下面一句 `fbound.prototype = this.prototype;`，
        // 已经修改了 fbound.prototype 为 绑定函数的 prototype，
        // 此时结果为 true，当结果为 true 的时候，this 指向实例。

        // 当作为普通函数时，
        // this 指向 window，self 指向绑定函数，
        // 此时结果为 false，当结果为 false 的时候，
        // this 指向绑定的 context。

        self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
    }

    fNOP.prototype = this.prototype;
    // 修改返回函数的 prototype 为绑定函数的 prototype，实例就可以继承函数的原型中的值
    fbound.prototype = new fNOP();

    return fbound;

};



//当 bind 返回的函数作为构造函数的时候，bind 时指定的 this 值会失效，但传入的参数依然生效
; (function () {
    //return
    var value = 2;

    var foo = {
        value: 1
    };

    function bar(name, age) {
        this.habit = 'shopping';
        console.log(this.value);
        console.log(name);
        console.log(age);
    }

    bar.prototype.friend = 'kevin';

    var bindFoo = bar.bind(foo, 'daisy');
    // =================================
    // bindFoo()
    // 1   //这个1说明当前的this是指向的foo
    // daisy // 这个是传入的第一个值
    // undefined // 这个是形参2 没传所以是没有的
    // =================================


    var obj = new bindFoo('18');
    // undefined // 这里是undefined是因为new改变了this的指向
    // daisy  // 还是绑定前传入的第一个值
    // 18 // 这里相当于是第二值
    console.log(obj.habit);
    console.log(obj.friend);
    // shopping // 相当于new的bar
    // kevin // 相当于new的bar

})();


// 模拟bind2的 知乎看到的
; (function () {
    return
    // 1、什么是bind
    // bind()方法创建一个新的函数，在bind()被调用时，
    // 这个新函数的this被指定 bind()的第一个参数，
    // 而其余参数将作为新函数的参数，供调用时使用。
    // 2 语法
    // function.bind(thisArg[, arg1[, arg2[, ...]]])
    // thisArg：被绑定到函数上的对象，即当调用绑定后的函数时，
    // 函数中的this关键字会指向该对象。
    // 如果thisArg参数为null或undefined，
    // 则this关键字将指向全局对象（在浏览器中通常是window对象）。
    // arg1, arg2, ...：要传递给函数的参数。这些参数将按照顺序传递给函数，并在调用函数时作为函数参数使用。
    // 3. 返回值
    // 返回一个原函数的拷贝，并拥有指定的this值和初始参数。

    this.name = 'guizimo'
    let obj = {
        name: 'zimo',
        getName: function () { return this.name }
    }

    console.log(obj.getName())    // zimo 现在是obj调用的所以this指向obj

    let newGetName = obj.getName
    console.log(newGetName())     // guizimo 现在就是一个普通函数了所以指向了window

    let bindGetName = newGetName.bind(obj)
    console.log(bindGetName())     // zimo 经过了bind函数,所以this还是指向obj


    /**
 * 手写bind
 * @returns {function(): any}
 */
    Function.prototype.myBind = function () {
        // 处理函数
        let args = Array.from(arguments);
        let thisArg = args.shift();
        // 暂存this
        let thisFunc = this; //这个其实就是调用的函数
        // 因为需要构造函数，所以不能是匿名函数了
        const fBound = function () {
            const newArgs = args.concat(Array.from(arguments));
            // 判断是否为构造函数
            console.log(this)
            thisArg = this instanceof fBound ? this : thisArg;
            return thisFunc.apply(thisArg, newArgs);
        }
        // 直接将原函数的prototype赋值给绑定函数
        fBound.prototype = this.prototype;
        // 返回
        return fBound;
    }
})();

// bind函数 使用new
; (function () {
    return
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }

    Point.prototype.toString = function () {
        return `${this.x},${this.y}`;
    };

    const p = new Point(1, 2);
    p.toString();
    console.log(p.toString())
    // '1,2'

    // thisArg 的值并不重要，因为它被忽略了
    const YAxisPoint = Point.bind(null, 0 /*x*/);

    const axisPoint = new YAxisPoint(5);
    axisPoint.toString(); // '0,5'

    axisPoint instanceof Point; // true
    axisPoint instanceof YAxisPoint; // true
    new YAxisPoint(17, 42) instanceof Point; // true
    const YAxisPoint2 = new YAxisPoint(17, 42)
    console.log(YAxisPoint2.toString())// 0 17
})();

// 此段代码主要帮助理解
// self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
// 为什么new的时候和new self()一致
; (function () {
    return
    let Foo = function (x, y) {
        this.x = x
        this.y = y
        console.log("我是foo函数拉", x, y)
    }
    Foo.prototype.set = function () {
        return `${this.x}`
    }
    console.log(new Foo(8, 9))
    let context = {}
    var fbound = function () {
        // =================================
        // 这里的this主要作用是:
        // this指向了new fbound(1,2)出来的实例
        // 然后再Foo函数在执行一遍.
        // 其实相当于 实例.x = x  实例.y = y
        // =================================
        // 这段代码可以帮助理解
        // function Point(x, y) {
        //     this.x = x;
        //     this.y = y;
        // }

        // Point.prototype.toString = function () {
        //     return `${this.x},${this.y}`;
        // };

        // let obj = {}
        // obj.__proto__ = Point.prototype
        // Point.apply(obj, [1, 2])
        // console.log(obj);
        // console.log(obj.toString());
        // =================================

        Foo.apply(this, Array.prototype.slice.call(arguments));
    }
    fbound.prototype = Foo.prototype
    console.log(fbound)
    let foo = new fbound(1, 2)

    console.log(foo)
})();

// bind了一次后,在次bind无效
; (function () {
    return
    Function.prototype.bind2 = function (context) {

        if (typeof this !== "function") {
            throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var self = this; // 当前调用的方法,就是需要绑定的函数 foo.set.bind(obj) 这里的self就是指foo.set
        var args = Array.prototype.slice.call(arguments, 1); // 获取后面的参数
        var fNOP = function () { };

        var fbound = function () {
            // 当作为构造函数时，this 指向实例，self 指向绑定函数，
            // 因为下面一句 `fbound.prototype = this.prototype;`，
            // 已经修改了 fbound.prototype 为 绑定函数的 prototype，
            // 此时结果为 true，当结果为 true 的时候，this 指向实例。

            // 当作为普通函数时，
            // this 指向 window，self 指向绑定函数，
            // 此时结果为 false，当结果为 false 的时候，
            // this 指向绑定的 context。

            self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
        }

        fNOP.prototype = this.prototype;
        // 修改返回函数的 prototype 为绑定函数的 prototype，实例就可以继承函数的原型中的值
        fbound.prototype = new fNOP();

        return fbound;

    };

    let one = {
        a: 1


    }
    let two = {
        a: 2
    }

    // 下面的模拟不考虑参数
    let foo = function () {
        console.log(this.a)
    }
    foo() // undefined
    let foo1 = foo.bind2(one)
    // ================================= 相当于
    // foo1 = function(){
    //     foo.apply(one, ...arguments);
    // }
    // =================================
    foo1() // 1
    let foo2 = foo1.bind2(two)
    // ================================= 相当于
    // foo2 = function(){
    //     foo1.apply(two, ...arguments);
    // }
    // =================================
    foo2() // 1 // 最终指向的还是foo, 离foo最近的绑定是one
})();